package osami.communication;

import java.util.Calendar;
import java.util.Vector;

import osami.communication.translate.FindServiceMessage;
import osami.communication.translate.Message;
import osami.communication.translate.OsamiExceptionMessage;
import osami.communication.translate.RemoteCallMessage;
import osami.communication.translate.TranslationException;
import osami.communication.translate.Translator;


/**
 * Superclass of all connections. Connection class represents a connection
 * between client and server nodes.
 * 
 * @author osami
 *
 */
public abstract class Connection
{
	protected OsamiIdentifier iIdentifier = null;
	protected Address iAddress = null;
	protected Activator iEngine = null;
	protected CommServer iCommServer = null;
	protected Store iStore = new Store();
	protected Translator iTranslator = null;
	
	/**
	 * Creates a new instance of Connection.
	 * 
	 * @param aEngine
	 * @param aCommServer
	 * @param aAddress
	 */
	public Connection( Activator aEngine, CommServer aCommServer, Address aAddress )
	{
		iEngine = aEngine;
		iCommServer = aCommServer;
		iAddress = aAddress;
	}
	
	/**
	 * Returns a String presentation of the Connection.
	 *
	 * @return String
	 */
	@Override
	public String toString() {
		return "Connection [iAddress=" + iAddress + ", iIdentifier="
				+ iIdentifier + ", iTranslator="
				+ iTranslator + "]";
	}




	/**
	 * Returns the identifier ot the connection.
	 * 
	 * @return OsamiIdentifier
	 */
	public OsamiIdentifier getIdentifier()
	{
		return iIdentifier;
	}
	
	/**
	 * Sets the identifier of the connection.
	 * 
	 * @param aIdentifier
	 */
	public void setIdentifier( OsamiIdentifier aIdentifier )
	{
		iIdentifier = aIdentifier;
	}
	
	/**
	 * Returns the address of the connection.
	 * 
	 * @return Address
	 */
	public Address getAddress()
	{
		return iAddress;
	}
	
	
	/**
	 * Producer & consumer -model's store object. Stores messages collected from the incoming stream
	 * 
	 */
	class Store
	{
		Vector<Message> msgBuffer = new Vector<Message>();

		/**
		 * Try to fetch an object from the store, and
		 * wait if appropriate object cannot (yet) be found
		 * 
		 * @return
		 * @throws TranslationException 
		 * @throws OsamiException 
		 */
		synchronized Message getObject( int aCallNumber, int aTimeOut ) throws TranslationException
		{
			double startingTime = Calendar.getInstance().getTimeInMillis();
			
			while ( true && ( startingTime + aTimeOut > Calendar.getInstance().getTimeInMillis() ) )
			{
				// if an object in the jsonBuffer has the same cN (call name) -number as the aObject, return the object
				for ( int i = 0; i < msgBuffer.size(); i++ )
				{
					Message message = msgBuffer.elementAt(i);
					
					if ( message.getCallNumber() == aCallNumber )
					{
						msgBuffer.remove(i);
						return message;
					}
				}

				// wait until someone adds an object into the msgBuffer
				// or timeout expires
				try
				{
					System.out.println( "Store.getObject: waiting for (max) " + aTimeOut + " milliseconds" );
					wait( aTimeOut );
					System.out.println( "Store.getObject: resumed from wait." );
				}
				catch( InterruptedException aException )
				{
					System.out.println( "Store.getObject: wait ended, InterruptedException caught :" + aException.getMessage() );

					//OsamiExceptionMessage msg = new OsamiExceptionMessage( "Interrupted (ID: " + Activator.getInstance().getIdentifier() + "): " + aException.getMessage() );
					//msg.setCallNumber( aCallNumber );
					//return msg;
				}
			}
			// TODO: this makes no sense
			OsamiExceptionMessage msg = null;//new OsamiExceptionMessage( "Timeout reached (" + aTimeOut + " ms),  (ID: " + Activator.getInstance().getIdentifier() + ")." );
			msg.setCallNumber( aCallNumber );
			return msg;
		}

		/**
		 * Insert object into the store and notify waiting threads.
		 * 
		 * @param object
		 */
		synchronized void putObject( Message object )
		{
			msgBuffer.add( object );
			notify();
		}
	}
	
	/**
	 * Closes the client socket and removes itself
	 * from communication server's client vector.
	 * 
	 */
	public abstract void unregister();

	/**
	 * Writes a message to the outStream. If the message is a request,
	 * the call blocks until a response is received.
	 * 
	 * @param aMessage
	 * @return Message
	 * @throws TranslationException 
	 */
	public abstract Message send( Message aMessage ) throws TranslationException;
	/**
	 * Passes a remote call message to OSAMI main to be handled.
	 * 
	 * @param aMessage
	 * @throws TranslationException 
	 * @throws OsamiException 
	 */
	protected abstract void remoteCall( RemoteCallMessage aMessage ) throws TranslationException, OsamiException;

	/**
	 * Sets the Translator for the Connection.
	 * 
	 * @param aTranslator
	 */
	public void setTranslator( Translator aTranslator )
	{
		System.out.println( "TCPIPConnection.setTranslator: Settings translator to: " + aTranslator.getName() );
		iTranslator = aTranslator;
	}
	/**
	 * Unregisters and closes the Connection.
	 */
	public abstract void closeConnection();

	/**
	 * Returns the translator of the Connection.
	 * @return Translator
	 */
	public Translator getTranslator()
	{
		return iTranslator;
	}
	
	/**
	 * Translates a String to Message.
	 * @param aMsg
	 * @return Message
	 * @throws TranslationException
	 */
	public Message translate( String aMsg ) throws TranslationException
	{
		return iTranslator.translate( aMsg );
	}

	/**
	 * Translates a Message to String
	 * @param aMsg
	 * @return String
	 * @throws TranslationException
	 */
	public String translate( Message aMsg ) throws TranslationException
	{
		return iTranslator.translate( aMsg );
	}
	
	/**
	 * Calculates the timeout.
	 * @param aMessage
	 * @return int
	 */
	protected int calculateTimeout( Message aMessage )
	{
		int timeout = 0;
		
		if ( aMessage.getClass() == RemoteCallMessage.class ||
				aMessage.getClass() == FindServiceMessage.class )
			timeout = ((RemoteCallMessage)aMessage).getRoute().size() * CommServer.TIMEOUT;
		else
			timeout = CommServer.TIMEOUT;
		
		return timeout;
	}
}